import { VantComponent } from '../common/component'
import { touch } from '../mixins/touch'
import { getAllRect, getRect, groupSetData, nextTick, requestAnimationFrame } from '../common/utils'
import { isDef } from '../common/validator'
import { useChildren } from '../common/relation'
VantComponent({
  mixins: [touch],
  classes: ['nav-class', 'tab-class', 'tab-active-class', 'line-class', 'wrap-class'],
  relation: useChildren('tab', function () {
    this.updateTabs()
  }),
  props: {
    sticky: Boolean,
    border: Boolean,
    swipeable: Boolean,
    titleActiveColor: String,
    titleInactiveColor: String,
    color: String,
    animated: {
      type: Boolean,
      observer() {
        this.children.forEach((child, index) =>
          child.updateRender(index === this.data.currentIndex, this),
        )
      },
    },
    lineWidth: {
      type: null,
      value: 40,
      observer: 'resize',
    },
    lineHeight: {
      type: null,
      value: -1,
    },
    active: {
      type: null,
      value: 0,
      observer(name) {
        if (name !== this.getCurrentName()) {
          this.setCurrentIndexByName(name)
        }
      },
    },
    type: {
      type: String,
      value: 'line',
    },
    ellipsis: {
      type: Boolean,
      value: true,
    },
    duration: {
      type: Number,
      value: 0.3,
    },
    zIndex: {
      type: Number,
      value: 1,
    },
    swipeThreshold: {
      type: Number,
      value: 5,
      observer(value) {
        this.setData({
          scrollable: this.children.length > value || !this.data.ellipsis,
        })
      },
    },
    offsetTop: {
      type: Number,
      value: 0,
    },
    lazyRender: {
      type: Boolean,
      value: true,
    },
    useBeforeChange: {
      type: Boolean,
      value: false,
    },
  },
  data: {
    tabs: [],
    scrollLeft: 0,
    scrollable: false,
    currentIndex: 0,
    container: null,
    skipTransition: true,
    scrollWithAnimation: false,
    lineOffsetLeft: 0,
    inited: false,
  },
  mounted() {
    requestAnimationFrame(() => {
      this.swiping = true
      this.setData({
        container: () => this.createSelectorQuery().select('.van-tabs'),
      })
      this.resize()
      this.scrollIntoView()
    })
  },
  methods: {
    updateTabs() {
      const { children = [], data } = this
      this.setData({
        tabs: children.map((child) => child.data),
        scrollable: this.children.length > data.swipeThreshold || !data.ellipsis,
      })
      this.setCurrentIndexByName(data.active || this.getCurrentName())
    },
    trigger(eventName, child) {
      const { currentIndex } = this.data
      const data = this.getChildData(currentIndex, child)
      if (!isDef(data)) {
        return
      }
      this.$emit(eventName, data)
    },
    onTap(event) {
      const { index } = event.currentTarget.dataset
      const child = this.children[index]
      if (child.data.disabled) {
        this.trigger('disabled', child)
        return
      }
      this.onBeforeChange(index).then(() => {
        this.setCurrentIndex(index)
        nextTick(() => {
          this.trigger('click')
        })
      })
    },
    // correct the index of active tab
    setCurrentIndexByName(name) {
      const { children = [] } = this
      const matched = children.filter((child) => child.getComputedName() === name)
      if (matched.length) {
        this.setCurrentIndex(matched[0].index)
      }
    },
    setCurrentIndex(currentIndex) {
      const { data, children = [] } = this
      if (!isDef(currentIndex) || currentIndex >= children.length || currentIndex < 0) {
        return
      }
      groupSetData(this, () => {
        children.forEach((item, index) => {
          const active = index === currentIndex
          if (active !== item.data.active || !item.inited) {
            item.updateRender(active, this)
          }
        })
      })
      if (currentIndex === data.currentIndex) {
        if (!data.inited) {
          this.resize()
        }
        return
      }
      const shouldEmitChange = data.currentIndex !== null
      this.setData({ currentIndex })
      requestAnimationFrame(() => {
        this.resize()
        this.scrollIntoView()
      })
      nextTick(() => {
        this.trigger('input')
        if (shouldEmitChange) {
          this.trigger('change')
        }
      })
    },
    getCurrentName() {
      const activeTab = this.children[this.data.currentIndex]
      if (activeTab) {
        return activeTab.getComputedName()
      }
    },
    resize() {
      if (this.data.type !== 'line') {
        return
      }
      const { currentIndex, ellipsis, skipTransition } = this.data
      Promise.all([getAllRect(this, '.van-tab'), getRect(this, '.van-tabs__line')]).then(
        ([rects = [], lineRect]) => {
          const rect = rects[currentIndex]
          if (rect == null) {
            return
          }
          let lineOffsetLeft = rects
            .slice(0, currentIndex)
            .reduce((prev, curr) => prev + curr.width, 0)
          lineOffsetLeft += (rect.width - lineRect.width) / 2 + (ellipsis ? 0 : 8)
          this.setData({ lineOffsetLeft, inited: true })
          this.swiping = true
          if (skipTransition) {
            // waiting transition end
            setTimeout(() => {
              this.setData({ skipTransition: false })
            }, this.data.duration)
          }
        },
      )
    },
    // scroll active tab into view
    scrollIntoView() {
      const { currentIndex, scrollable, scrollWithAnimation } = this.data
      if (!scrollable) {
        return
      }
      Promise.all([getAllRect(this, '.van-tab'), getRect(this, '.van-tabs__nav')]).then(
        ([tabRects, navRect]) => {
          const tabRect = tabRects[currentIndex]
          const offsetLeft = tabRects
            .slice(0, currentIndex)
            .reduce((prev, curr) => prev + curr.width, 0)
          this.setData({
            scrollLeft: offsetLeft - (navRect.width - tabRect.width) / 2,
          })
          if (!scrollWithAnimation) {
            nextTick(() => {
              this.setData({ scrollWithAnimation: true })
            })
          }
        },
      )
    },
    onTouchScroll(event) {
      this.$emit('scroll', event.detail)
    },
    onTouchStart(event) {
      if (!this.data.swipeable) return
      this.swiping = true
      this.touchStart(event)
    },
    onTouchMove(event) {
      if (!this.data.swipeable || !this.swiping) return
      this.touchMove(event)
    },
    // watch swipe touch end
    onTouchEnd() {
      if (!this.data.swipeable || !this.swiping) return
      const { direction, deltaX, offsetX } = this
      const minSwipeDistance = 50
      if (direction === 'horizontal' && offsetX >= minSwipeDistance) {
        const index = this.getAvaiableTab(deltaX)
        if (index !== -1) {
          this.onBeforeChange(index).then(() => this.setCurrentIndex(index))
        }
      }
      this.swiping = false
    },
    getAvaiableTab(direction) {
      const { tabs, currentIndex } = this.data
      const step = direction > 0 ? -1 : 1
      for (let i = step; currentIndex + i < tabs.length && currentIndex + i >= 0; i += step) {
        const index = currentIndex + i
        if (index >= 0 && index < tabs.length && tabs[index] && !tabs[index].disabled) {
          return index
        }
      }
      return -1
    },
    onBeforeChange(index) {
      const { useBeforeChange } = this.data
      if (!useBeforeChange) {
        return Promise.resolve()
      }
      return new Promise((resolve, reject) => {
        this.$emit(
          'before-change',
          Object.assign(Object.assign({}, this.getChildData(index)), {
            callback: (status) => (status ? resolve() : reject()),
          }),
        )
      })
    },
    getChildData(index, child) {
      const currentChild = child || this.children[index]
      if (!isDef(currentChild)) {
        return
      }
      return {
        index: currentChild.index,
        name: currentChild.getComputedName(),
        title: currentChild.data.title,
      }
    },
  },
})
